import inspect
import traceback
from copy import deepcopy
from pprint import pformat
from types import GenericAlias
from typing import get_origin, Annotated
import json
import requests
import random
import time
from tool.comfyUI_api import ComfyUIApi
from tool.email_util import EmailUtil
import subprocess
import asyncio
from wcferry import Wcf
from win32api import ShellExecute
import os,re
from configuration import Config
from utils import update_dict
from .code_kernel import CodeKernel, execute

_TOOL_HOOKS = {}
_TOOL_DESCRIPTIONS = {}
_TOOL_QW_DESCRIPTIONS = []
_TOOL_OP_DESCRIPTIONS = []
_TOOL_LM4_DESCRIPTIONS = []

def nested_object_to_dict(obj):
    if isinstance(obj, list):
        return [nested_object_to_dict(x) for x in obj]
    if isinstance(obj, dict):
        return {k: nested_object_to_dict(v) for k, v in obj.items()}
    if  obj and type(obj) not in (int, float, str):
        return nested_object_to_dict(vars(obj))
    else:
        return obj

def register_tool(func: callable):
    tool_name = func.__name__
    tool_description = inspect.getdoc(func).strip()
    python_params = inspect.signature(func).parameters
    tool_params = []
    opai_params = {}
    otye={"str":'string',"int":'number',"dict":'object',"list":'array',"bool":'boolean',"None":'null'}

    for name, param in python_params.items():
        annotation = param.annotation
        if annotation is inspect.Parameter.empty:
            #raise TypeError(f"Parameter `{name}` missing type annotation")
            continue
        if get_origin(annotation) != Annotated:
            raise TypeError(f"Annotation type for `{name}` must be typing.Annotated")

        typ, (description, required) = annotation.__origin__, annotation.__metadata__
        typ: str = str(typ) if isinstance(typ, GenericAlias) else typ.__name__
        if not isinstance(description, str):
            raise TypeError(f"Description for `{name}` must be a string")
        if not isinstance(required, bool):
            raise TypeError(f"Required for `{name}` must be a bool")

        tool_params.append({
            "name": name,
            "description": description,
            "type": typ,
            "required": required
        })
        ntype=otye[str(typ)] if typ in otye else typ
        opai_params[name] = {
            "type": ntype,
            "description": description,
            "required":[name] if required else []
            #"enum": ["celsius", "fahrenheit"]
        }
    tool_def = {
        "name": tool_name,
        "description": tool_description,
        "name_for_human": tool_description,
        "description_for_model": tool_description + " Format the arguments as a JSON object.",
        "params": tool_params,
        "parameters":tool_params
    }
    opai_tool_def={
        "name": tool_name,
        "description": tool_description,
        "params": tool_params,
        "parameters":{"type": 'object',"properties":opai_params}
    }

    glm4_tool_def={
        "type": "function",
        "function": {
            "name": tool_name,
            "description": tool_description,
            "params": tool_params,
            "parameters":{"type": 'object',"properties":opai_params}
        }
    }

    #print("[registered tool] " + pformat(tool_def))
    _TOOL_HOOKS[tool_name] = func
    _TOOL_DESCRIPTIONS[tool_name] = tool_def
    _TOOL_QW_DESCRIPTIONS.append(tool_def)
    _TOOL_OP_DESCRIPTIONS.append(opai_tool_def)
    _TOOL_LM4_DESCRIPTIONS.append(glm4_tool_def)


    return func


def dispatch_tool(tool_name: str, tool_params: dict) -> str:
    if tool_name not in _TOOL_HOOKS:
        return f"Tool `{tool_name}` not found. Please use a provided tool."
    tool_call = _TOOL_HOOKS[tool_name]
    try:
        if len(tool_params) > 0:
            ret = tool_call(**tool_params)
        else:
            ret = tool_call()

    except:
        ret = traceback.format_exc()
    return ret


def get_tools() -> dict:
    return deepcopy(_TOOL_DESCRIPTIONS)

def get_qw_tools() -> list:
    return deepcopy(_TOOL_QW_DESCRIPTIONS)

def get_op_tools() -> list:
    return deepcopy(_TOOL_OP_DESCRIPTIONS)

def get_lm4_tools() -> list:
    return deepcopy(_TOOL_LM4_DESCRIPTIONS)


# Tool Definitions

# @register_tool
# def random_number_generator(
#         seed: Annotated[int, 'The random seed used by the generator', True],
#         range: Annotated[tuple[int, int], 'The range of the generated numbers', True],
# ) -> int:
#     """
#     Generates a random number x, s.t. range[0] <= x < range[1]
#     """
#     if not isinstance(seed, int):
#         raise TypeError("Seed must be an integer")
#     if not isinstance(range, tuple) and not isinstance(range, list):
#         raise TypeError("Range must be a tuple")
#     if not isinstance(range[0], int) or not isinstance(range[1], int):
#         raise TypeError("Range must be a tuple of integers")

#     import random
#     return random.Random(seed).randint(*range)

#运行cmd命令
@register_tool
def run_cmd(cmd:Annotated[str, 'cmd命令内容', True]):
    '''执行cmd命令'''
    try:
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        out, err = p.communicate()
        return {"success": True,"res": out.decode('utf-8'),"error":err.decode('utf-8'),"res_type": "text"}
    except Exception as e:
        answer = {"success": False,"res": "执行异常" ,"res_type": "text"}
        return answer

kernel = CodeKernel()
#执行python代码
@register_tool
def execute_python_code(
        code: Annotated[str, 'The python code to be executed', True],
) -> str:
    """
    用jupyter执行python代码
    """
    res_type, res = execute(code, kernel)
    if res==None or res=='':
        res="执行完成"
    answer = {"success": True,"res": res ,"res_type": res_type}
    return answer

@register_tool
def get_weather(
        city_name: Annotated[str, 'The name of the city to be queried', True],
) -> str:
    """
    Get the current weather for `city_name`
    """

    if not isinstance(city_name, str):
        raise TypeError("City name must be a string")

    key_selection = {
        "current_condition": ["temp_C", "FeelsLikeC", "humidity", "weatherDesc", "observation_time"],
    }
    import requests
    try:
        resp = requests.get(f"https://wttr.in/{city_name}?format=j1")
        resp.raise_for_status()
        resp = resp.json()
        ret = {k: {_v: resp[k][0][_v] for _v in v} for k, v in key_selection.items()}
    except:
        import traceback
        ret = "Error encountered while fetching weather data!\n" + traceback.format_exc()

    return str(ret)

wcf = Wcf(debug=True)

@register_tool
def read_wechat_message(sel):
    """阅读微信未读的消息内容"""
    types=wcf.get_msg_types()
    msgs=[]
    for msg in sel.msg_list:
        user=wcf.get_info_by_wxid(msg.sender)
        msgs.append({'type':types[msg.type],'id':msg.id,'content':msg.content,'filePath':msg.extra,'roomid':msg.roomid,
              'senderName':user['remark'] if user['remark'] else user['name']})
    answer = {"success": True,"res": msgs ,"res_type": "text"}
    sel.msg_list=[]
    return answer 

@register_tool
def get_wechat_unread_count(sel):
    """获取微信未读消息的数量"""
    answer = {"success": True,"res": len(sel.msg_list) ,"res_type": "text"}
    sel.is_new_msg=False
    return answer 

@register_tool
def send_wechat_message(
        to_user: Annotated[str, '接收人名称', True],
        message: Annotated[str, '消息内容', False]=None,
        file_name:Annotated[str, '文件名称',False]=None):
    '''
    发送微信消息给`to_user`消息内容为`message`发送文件或图片名称为`file_name`
    '''
    try:
        #如果消息内容和文件名称都为空
        if not message and not file_name:
            return {"success": False,"res": "消息内容和文件名称至少一个不能为空","res_type": "text"}
        friends=wcf.get_friends()
        for friend in friends:
            if friend['name'] == to_user or to_user==friend['remark']:
                #判断文件是否为图片
                status=0
                if file_name:
                    if file_name.split(".")[-1] not in ["jpg","png","bmp","gif","jpeg"]:
                        status+=wcf.send_image(os.path.join(Config().CHAT_ROBOT['basePath'],file_name),friend['wxid'])
                    else:
                        status+=wcf.send_file(os.path.join(Config().CHAT_ROBOT['basePath'],file_name),friend['wxid'])
                if  message:
                    status+=wcf.send_text(message,friend['wxid'])
                if status!=0:
                    answer = {"success": False,"res": "发送失败","res_type": "text"}
                else:
                    answer = {"success": True,"res": "发送成功","res_type": "text"}
                return answer
                
        answer = {"success": False,"res": "发送失败,未找到接收人" ,"res_type": "text"}
        return answer
    except:
        answer = {"success": False,"res": "发送异常" ,"res_type": "text"}
        return answer
            
    
    
softwares={
    "微信": r"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe",
    "浏览器": r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
    "开发工具": r"I:\Programs\Microsoft VS Code\Code.exe",
    "绘图工具": r"F:\ComfyUI\启动.bat",
    "记事本": "notepad",
    "计算器": "calc",
    "控制面版": "control",
    "画图": "mspaint",
}
#启动软件
@register_tool
def start_software(software_name: Annotated[str, '软件名称 e.g. 微信，浏览器，开发工具，绘图工具，记事本，计算器，控制面版，画图', True]) -> dict:
    '''
    启动或打开`software_name`软件
    '''
    # 创建事件循环并运行异步任务
    try:
        ShellExecute(None, "open", softwares[software_name], None, None, 1)
    except:
        answer = {"success": False,"res": "启动失败" ,"res_type": "text"}
        return answer
            
    answer = {"success": True,"res": "启动成功" ,"res_type": "text"}
    return answer

@register_tool
def resetting_chat_record() -> dict:
    '''重置聊对话'''
    answer = {"success": True,"res": "重置成功" ,"res_type": "text"}
    return answer


@register_tool
def generate_image(prompt: Annotated[str, '要生成图片的英文提示词', True],
                      style_name: Annotated[str, '风格名称 e.g. 漫画，抽象，水墨画，滴墨，未来派', False]=None) -> dict:
    '''
    生成图片`prompt`提示词和风格`style_name`的图片
    '''
    if not Config().COMFY_UI:
        answer = {"success": False,"res": "未配置 ComfyUI " ,"res_type": "text"}
        return answer
    if os.path.exists(Config().COMFY_UI['base_json_path']):
        answer = {"success": False,"res": "ComfyUI工作流文件不存在 " ,"res_type": "text"}
        return answer
    styles=''
    if style_name and os.path.exists(Config().COMFY_UI['style_json_path']):
        f = open(Config().COMFY_UI['style_json_path'],'r', encoding='utf-8')
        data = json.load(f)
        f.close()
        if style_name in data:
            styles=data[style_name]
        else:
            for k,v in data.items():
                if k.find(style_name)>-1:
                    styles=v
                    break

    
    with open(Config().COMFY_UI['base_json_path'], "r", encoding="utf-8") as f:
        data2 = json.load(f)
        if Config().COMFY_UI.get('seed',None):
            update_dict(data2,Config().COMFY_UI['seed'],''.join(random.sample('123456789012345678901234567890',14)))
        if Config().COMFY_UI.get('styles',None):
            update_dict(data2,Config().COMFY_UI['styles'],(','+styles) if styles else '')
        if Config().COMFY_UI.get('prompt',None):
            update_dict(data2,Config().COMFY_UI['prompt'],prompt)
            
        cfui=ComfyUIApi(Config().COMFY_UI['server_address'])
        images = cfui.get_images(data2)
        try:
            filename = images[0]['filename']
            filePath = os.path.join(Config().CHAT_ROBOT['basePath'], filename)
            images[0]['image'].save(filePath)
            ShellExecute(None, "open", filePath, None, None, 1)
        except Exception as e:
            return {'res':'生成异常'+str(e),'res_type':'text'}
        return {'res':images[0]['filename'],'res_type':'image'}
    
#发送邮件
@register_tool
def send_email(
        to_user: Annotated[str, '接收人名称', True],
        Subject: Annotated[str, '邮件标题', False]='桌面智能助手发送',
        content: Annotated[str, '邮件内容', False]=None,
        file_name:Annotated[str, '文件名称',False]=None) -> dict:
    '''
    发送邮件给`to_user`邮件内容为`content`文件名称为`file_name`
    '''
    if to_user not in Config().EMAIL['user_list']:
        return {"success": False,"res": "邮件接收人不存在","res_type": "text"}
    if not content and not file_name:
        return {"success": False,"res": "消息内容和文件名称至少一个不能为空","res_type": "text"}
    
    try:
        eu=EmailUtil(Config().EMAIL['server_host'], Config().EMAIL['server_port'], Config().EMAIL['server_user'], Config().EMAIL['server_pwd'])
        eu.initMsg(Subject)
        if content:
            eu.msg_text_attach(content)
        else:
            eu.msg_text_attach(Subject)
        if file_name:
            #判断文件是否存在且是否为图片
            if os.path.exists(os.path.join(Config().CHAT_ROBOT['basePath'],file_name)):
                if file_name.split(".")[-1] not in ["jpg","png","bmp","gif","jpeg"]:
                    eu.add_image(os.path.join(Config().CHAT_ROBOT['basePath'],file_name), '1')
                else:
                    eu.send_annex_file(os.path.join(Config().CHAT_ROBOT['basePath'],file_name))
        eu.send([Config().EMAIL['user_list'][to_user]])
        eu.quit()
        return {"success": True,"res": "邮件发送成功" ,"res_type": "text"}
    except Exception as e:
        print(e)
        answer = {"success": False,"res": "邮件发送异常" ,"res_type": "text"}
        return answer

if __name__ == "__main__":
    print(dispatch_tool("get_weather", {"city_name": "beijing"}))
    print(get_tools())
